#include <linux/sched/debug.h>
#include <linux/sched.h>
#include <linux/jiffies.h>
#include <linux/timer.h>
#include <linux/hrtimer.h>

struct process_timer
{
    struct timer_list timer;
    struct task_struct *task;
};

static void process_timeout(struct timer_list *t)
{
    struct process_timer *timeout = from_timer(timeout, t, timer);

    wake_up_process(timeout->task);
}

long schedule_timeout(int timeout)
{
    struct process_timer timer;
    unsigned long expire;

    if (timeout < 0)
    {
        schedule();
    }
    else
    {
        timer.task = current;

        expire = jiffies + timeout;

        timer_setup(&timer.timer, process_timeout, 0);
        timer.timer.expires = expire;
        add_timer(&timer.timer);

        schedule();

        del_timer_sync(&timer.timer);

        timeout = expire - jiffies;
    }

    return (timeout < 0) ? 0 : timeout;
}

long __sched schedule_timeout_uninterruptible(int timeout)
{
    __set_current_state(TASK_UNINTERRUPTIBLE);

    return schedule_timeout(timeout);
}

/**
 * schedule_hrtimeout_range_clock - sleep until timeout
 * @expires:	timeout value (ktime_t)
 * @delta:	slack in expires timeout (ktime_t)
 * @mode:	timer mode
 * @clock_id:	timer clock to be used
 *
 * Details are explained in schedule_hrtimeout_range() function description as
 * this function is commonly used.
 */
int __sched schedule_hrtimeout_range_clock(ktime_t *expires, u64 delta,
                                           const enum hrtimer_mode mode, clockid_t clock_id)
{
    struct hrtimer_sleeper t;

    /*
     * Optimize when a zero timeout value is given. It does not
     * matter whether this is an absolute or a relative time.
     */
    if (expires && *expires == 0)
    {
        __set_current_state(TASK_RUNNING);
        return 0;
    }

    /*
     * A NULL parameter means "infinite"
     */
    if (!expires)
    {
        schedule();
        return -EINTR;
    }

    hrtimer_setup_sleeper_on_stack(&t, clock_id, mode);
    hrtimer_set_expires_range_ns(&t.timer, *expires, delta);
    hrtimer_sleeper_start_expires(&t, mode);

    if (likely(t.task))
        schedule();

    hrtimer_cancel(&t.timer);
    destroy_hrtimer_on_stack(&t.timer);

    __set_current_state(TASK_RUNNING);

    return !t.task ? 0 : -EINTR;
}

/**
 * schedule_hrtimeout_range - sleep until timeout
 * @expires:	timeout value (ktime_t)
 * @delta:	slack in expires timeout (ktime_t)
 * @mode:	timer mode
 *
 * Make the current task sleep until the given expiry time has
 * elapsed. The routine will return immediately unless
 * the current task state has been set (see set_current_state()).
 *
 * The @delta argument gives the kernel the freedom to schedule the
 * actual wakeup to a time that is both power and performance friendly
 * for regular (non RT/DL) tasks.
 * The kernel give the normal best effort behavior for "@expires+@delta",
 * but may decide to fire the timer earlier, but no earlier than @expires.
 *
 * You can set the task state as follows -
 *
 * %TASK_UNINTERRUPTIBLE - at least @timeout time is guaranteed to
 * pass before the routine returns unless the current task is explicitly
 * woken up, (e.g. by wake_up_process()).
 *
 * %TASK_INTERRUPTIBLE - the routine may return early if a signal is
 * delivered to the current task or the current task is explicitly woken
 * up.
 *
 * The current task state is guaranteed to be TASK_RUNNING when this
 * routine returns.
 *
 * Returns: 0 when the timer has expired. If the task was woken before the
 * timer expired by a signal (only possible in state TASK_INTERRUPTIBLE) or
 * by an explicit wakeup, it returns -EINTR.
 */
int __sched schedule_hrtimeout_range(ktime_t *expires, u64 delta,
                                     const enum hrtimer_mode mode)
{
    return schedule_hrtimeout_range_clock(expires, delta, mode,
                                          CLOCK_MONOTONIC);
}

/**
 * schedule_hrtimeout - sleep until timeout
 * @expires:	timeout value (ktime_t)
 * @mode:	timer mode
 *
 * See schedule_hrtimeout_range() for details. @delta argument of
 * schedule_hrtimeout_range() is set to 0 and has therefore no impact.
 */
int __sched schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode)
{
    return schedule_hrtimeout_range(expires, 0, mode);
}
